// Copyright Epic Games, Inc. All Rights Reserved.

#pragma once

#include <atomic>
#include <condition_variable>
#include <deque>
#include <mutex>

namespace zen {

template<typename T>
class BlockingQueue
{
public:
	BlockingQueue() = default;

	~BlockingQueue() { CompleteAdding(); }

	void Enqueue(T&& Item)
	{
		{
			std::lock_guard Lock(m_Lock);
			m_Queue.emplace_back(std::move(Item));
		}

		m_NewItemSignal.notify_one();
	}

	bool WaitAndDequeue(T& Item)
	{
		std::unique_lock Lock(m_Lock);
		if (m_Queue.empty())
		{
			if (m_CompleteAdding)
			{
				return false;
			}
			m_NewItemSignal.wait(Lock, [this]() { return !m_Queue.empty() || m_CompleteAdding; });
			if (m_Queue.empty())
			{
				ZEN_ASSERT(m_CompleteAdding);
				return false;
			}
		}
		Item = std::move(m_Queue.front());
		m_Queue.pop_front();
		return true;
	}

	void CompleteAdding()
	{
		std::unique_lock Lock(m_Lock);
		if (!m_CompleteAdding)
		{
			m_CompleteAdding = true;

			Lock.unlock();
			m_NewItemSignal.notify_all();
		}
	}

	std::size_t Size() const
	{
		std::unique_lock Lock(m_Lock);
		return m_Queue.size();
	}

private:
	mutable std::mutex		m_Lock;
	std::condition_variable m_NewItemSignal;
	std::deque<T>			m_Queue;
	bool					m_CompleteAdding = false;
};

}  // namespace zen
